<?php

/*
 * This file is part of the prestaBreadcrumb package.
 * (c) Julien Fusco <jfusco@prestaconcept.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */


/**
 * presta breadcrumb
 * 
 * @author	Julien Fusco <jfusco@prestaconcept.net>
 * @version	SVN: $Id: $ 1.0 - 7 déc. 2009 - Julien Fusco <jfusco@prestaconcept.net>
 * @since	7 déc. 2009 - Julien Fusco <jfusco@prestaconcept.net>
 */
class prestaBreadcrumb
{
	/**
	 * The singleton instance of this object
	 *
	 * @var prestaBreadcrumb
	 */
	protected static $instance;
	
	/**
	 * An array of prestaBreadcrumbItem
	 * 
	 * @var Array
	 */
	protected $items = array();
	
	/**
	 * The html/text to place between each breadcrumb path
	 * 
	 * @var string
	 */
	protected $separator;
	
	/**
	 * The template to sprintf each breadcrumb link
	 * 
	 * @var string
	 */
	protected $template;
	
	/**
	 * The last state of the breadcrumb
	 * 
	 * @var array
	 */
	protected $lastState;
	
	const DEFAULT_ROOT		= 'Home';
	const DEFAULT_ROOT_URI	= '@homepage';
	const DEFAULT_SEPARATOR = '&nbsp;>&nbsp;';
	const DEFAULT_TEMPLATE	= '%s';
	
	/**
	 * Protected constructor because there is really no need to instantiate this object
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 */
	protected function __construct()
	{
	}
	
	/**
	 * Get the singleton instance
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @return 	prestaBreadcrumb
	 */
	public static function getInstance()
	{
		if(!self::$instance instanceof prestaBreadcrumb)
		{
			self::$instance = new prestaBreadcrumb();
			// Define the root
			self::$instance->setRoot(sfConfig::get('app_prestaBreadcrumb_home', self::DEFAULT_ROOT), sfConfig::get('app_prestaBreadcrumb_home_uri', self::DEFAULT_ROOT_URI));
			// Define the separator
			self::$instance->setSeparator(sfConfig::get('app_prestaBreadcrumb_separator', self::DEFAULT_SEPARATOR));
			// Define the template
			self::$instance->setTemplate(sfConfig::get('app_prestaBreadcrumb_template', self::DEFAULT_TEMPLATE));
		}
		
		return self::$instance;
	}
	
	/**
	 * Add an item
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @param 	string $text
	 * @param 	string $uri
	 * @return 	prestaBreadcrumb
	 */
	public function addItem($text, $uri = null) 
	{
		array_push($this->items, new prestaBreadcrumbItem($text, $uri));
		return $this;
	}
	
	/**
	 * Add an array of items
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @param 	array $items
	 * @return 	prestaBreadcrumb
	 */
	public function addItemsByArray($items)
	{
		if (!is_array($items))
		{
			throw new sfException('You must passed an array to ->addItemsByArray().');
		}
		
		foreach ($items as $item)
		{
			$this->addItem($item['text'], $item['uri']);
		}
		
		return $this;
	}
	
	/**
	 * Delete all existings items, excepted the root item
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 * 
	 * @return 	prestaBreadcrumb
	 */
	public function clearItems() 
	{
		$root = $this->items[0];
		$this->items = array();
		$this->items[0] = $root;
		return $this;
	}
	
	/**
	 * Retrieve an array of prestaBreadcrumbItem
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @param 	int $offset
	 * @return 	Array
	 */
	public function getItems($offset = 0) 
	{
		return array_slice($this->items, $offset);
	}
	
	/**
	 * Return the number of items
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @return 	int
	 */
	public function countItems()
	{
		return is_array($this->items) ? count($this->items) : 0;
	}
	
	/**
	 * Redefine the root item
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @param 	$text
	 * @param 	$uri
	 * @return 	prestaBreadcrumb
	 */
	public function setRoot($text, $uri) 
	{
		$this->items[0] = new prestaBreadcrumbItem($text, $uri);
		return $this;
	}
	
	/**
	 * Return the separator
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @return 	string
	 */
	public function getSeparator()
	{
		return $this->separator;
	}
	
	/**
	 * Set the separator
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @param 	string $separator
	 */
	public function setSeparator($separator)
	{
		$this->separator = $separator;
	}
	
	/**
	 * Set the template
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @param 	$template
	 * @return 	prestaBreadcrumb
	 */
	public function setTemplate($template)
	{
		if (is_null($template))
		{
			throw new sfException('The template cannot be null.');
		}
		
		$this->template = $template;
		return $this;
	}
	
	/**
	 * Get the template
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @return 	string
	 */
	public function getTemplate()
	{
		return $this->template;
	}
	
	/**
	 * Get the last state of the breadcrumb
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 *
	 * @return 	Array of prestaBreadcrumbItem
	 */
	public function getLastState() 
	{
		return $this->lastState;
	}
	
	/**
	 * Save the last state of the breadcrumb
	 * 
	 * @author 	Julien Fusco
	 * @since 	7 déc. 2009
	 * @version 1.0 - 7 déc. 2009 - Julien Fusco
	 */
	public function saveLastState()
	{
		$this->lastState = $this->items;
	}
	
	/**
	 * Render the breadcrumb
	 * 
	 * @author	Julien Fusco <jfusco@prestaconcept.net>
	 * @version	2.0 - 26 avr. 2010 - Mikael Randy <mrandy@prestaconcept.net>
	 * @since	07 dec. 2010 - Julien Fusco <jfusco@prestaconcept.net>
	 * @return 	string - The breadcrumb to display
	 */
	public function render() 
	{
		$this->saveLastState();
		$breadcrumb = ''; 		// The string to return
		$a_items = $this->getItems();
		
		// Get the last element of the items table, for specific action, but only if there is other item than the root
		$last_item = ( count($a_items) > 1)
			? array_pop( $a_items )
			: null;
		
		$first = true;
		foreach ($a_items as $o_prestaBreadcrumbItem)
		{
			$separator 	= ($first) ? '' : $this->getSeparator();
			$breadcrumb .= $separator.$this->renderItem( $o_prestaBreadcrumbItem, array('is_link' => true) );
			$first = false;
		}
		
		if( !is_null($last_item) )
			$breadcrumb .= $this->getSeparator().$this->renderLastItem($last_item);
		
		return $breadcrumb;
	}
	
	
	/**
	 * Render the last item specifically
	 * 
	 * Get the last item specific configuration, and construct specifi rendering option of the item
	 * 
	 * @author	Mikael Randy <mrandy@prestaconcept.net>
	 * @version	1.0 - 26 avr. 2010 - Mikael Randy <mrandy@prestaconcept.net>
	 * @since	26 avr. 2010 - Mikael Randy <mrandy@prestaconcept.net>
	 * @param 	prestaBreadcrumbItem $o_prestaBreadcrumbItem - The last item of the breadcrumb
	 * @return 	string - The last item render
	 */
	public function renderLastItem(prestaBreadcrumbItem $o_prestaBreadcrumbItem)
	{
		// Get the last element specific configuration
		$a_last_element_conf = sfConfig::get('app_prestaBreadcrumb_last', array());
			// Check if item have to be displayed as a link
		$is_link 	= array_key_exists('is_link', $a_last_element_conf) ? $a_last_element_conf['is_link'] : false;
			// Construct the HTML render option for the item
		$html_class = array_key_exists('html_class', $a_last_element_conf) ? $a_last_element_conf['html_class'] : null;
		$html_id  	= array_key_exists('html_id', $a_last_element_conf) ? $a_last_element_conf['html_id'] : null;
		$html_option = array();
		if( !is_null($html_class) && !empty($html_class) )
			$html_option['class'] = $html_class;
		if( !is_null($html_id) && !empty($html_id) )
			$html_option['id'] = $html_id;
			
		return $this->renderItem( $o_prestaBreadcrumbItem, array('is_link' => $is_link), $html_option );
	}
	
	
	/**
	 * Render an item of the breadcrumb
	 * 
	 * Render the given item and allow some configuration. Can display text or complete link, can hide separator.
	 * If an item dont have uri, dont try to display link
	 * $option accept some values :
	 *   * $option['is_link'] : if TRUE, the item will be rendered as a complete link, simple text otherwise
	 * $html_option is like standard html option array in Symfony
	 * 
	 * @author	Mikael Randy <mrandy@prestaconcept.net>
	 * @version	1.0 - 26 avr. 2010 - Mikael Randy <mrandy@prestaconcept.net>
	 * @since	26 avr. 2010 - Mikael Randy <mrandy@prestaconcept.net>
	 * @param 	prestaBreadcrumbItem $o_prestaBreadcrumbItem - Item to render
	 * @param 	array $option - rendering options
	 * @param 	array $html_option - HTML rendering options
	 * @return 	string - The item rendered
	 */
	public function renderItem(prestaBreadcrumbItem $o_prestaBreadcrumbItem, $option=array(), $html_option=array())
	{
		$uri = $o_prestaBreadcrumbItem->getUri();
		
		// Check if the item have to be displayed as a link, and if the item have an URI to construct the link
		$item_text 	= (array_key_exists('is_link', $option) &&  $option['is_link'] !== false && !empty($uri) )
			? link_to($o_prestaBreadcrumbItem->getText(), $o_prestaBreadcrumbItem->getUri(), $html_option)
			: content_tag('span', $o_prestaBreadcrumbItem->getText(), $html_option);
			
		return sprintf($this->getTemplate(), $item_text);
	}
}
